package info.niwota.web.bt;

import info.niwota.web.Debug;
import info.niwota.web.proxy.ProxyHandler;
import info.niwota.web.proxy.ProxyServer;
import info.niwota.web.proxy.ServerSocketThread;
import info.niwota.web.proxy.Uri;
import info.niwota.web.util.ConsoleFrame;
import info.niwota.web.util.SettingsDialog;

import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import x.org.apache.http.HttpRequest;
import x.org.apache.http.HttpResponse;
import x.org.apache.http.HttpStatus;
import x.org.apache.http.entity.ContentProducer;
import x.org.apache.http.entity.EntityTemplate;
import x.org.apache.http.protocol.HttpContext;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class Main {
	private static final File top = getTop();
	private static final Gson gson = new Gson();
	private static final File cachedDevices = new File(top, "devices.json");
	private static final File settings = new File(top, "settings.json");

	private static ProxyServer server;
	private static ServerSocketThread proxy;


	private static File getTop() {
		File home = new File(System.getProperty("user.home"));
		String user = System.getProperty("user.name");
		File top = new File(home, "." + user);
		if (!top.exists()) {
			top.mkdirs();
		}
		return top;
	}

	private static String readFile(File f) throws IOException {
		StringBuilder sb = new StringBuilder();
		BufferedReader r = new BufferedReader(new FileReader(f));
		char[] buf = new char[1024];
		int read = 0;
		try {
			while ((read = r.read(buf)) != -1) {
				sb.append(buf, 0, read);
			}
		} finally {
			r.close();
		}
		return sb.toString();
	}

	private static void writeFile(File f, String s) throws IOException {
		BufferedWriter w = new BufferedWriter(new FileWriter(f));
		try {
			w.write(s);
		} finally {
			w.close();
		}
	}

	private static List<BluetoothAddress> loadDevices() throws Exception {
		File f = cachedDevices;
		if (f.exists()) {
			String s = readFile(f);
			Type listType = new TypeToken<List<BluetoothAddress>>() {
			}.getType();
			List<BluetoothAddress> l = gson.fromJson(s, listType);
			for (Iterator<BluetoothAddress> i = l.iterator(); i.hasNext();) {
				BluetoothAddress a = i.next();
				if (a.getAddress() == null) {
					i.remove();
				}
			}
			return l;
		}
		return new ArrayList<BluetoothAddress>();
	}

	private static void storeDevices(List<BluetoothAddress> devices)
			throws Exception {
		final File f = cachedDevices;
		StringBuilder sb = new StringBuilder();
		Type listType = new TypeToken<List<BluetoothAddress>>() {
		}.getType();
		gson.toJson(devices, listType, sb);
		writeFile(f, sb.toString());
	}
	
	private static BluewebSettings loadSettings() throws Exception {
		File f = settings;
		if (f.exists()) {
			String s = readFile(f);
			return gson.fromJson(s, BluewebSettings.class);
		}
		BluewebSettings cfg = new BluewebSettings();
		cfg.proxyAddress = null;
		cfg.proxyPort = 18080;
		cfg.serverPort = 10080;
		
		return cfg;
	}
	
	private static void storeSettings(BluewebSettings cfg) throws Exception {
		final File f = settings;
		StringBuilder sb = new StringBuilder();
		gson.toJson(cfg, BluewebSettings.class, sb);
		writeFile(f, sb.toString());
	}

	public static class BluetoothProxyHandler extends ProxyHandler {

		protected boolean handleLocal(final Uri uri, final HttpContext context,
				final HttpRequest request, final HttpResponse response)
				throws Exception {

			if (!isLocal(uri)) {
				return false;
			}

			final String path = uri.getPath();

			final boolean local = (path.equals("/") || path.equals("/devices"));
			if (!local) {
				return false;
			}

			//
			boolean refresh = Boolean.parseBoolean(uri.getParameter("refresh"));

			final List<BluetoothAddress> addresses = loadDevices();

			final List<RemoteDevice> devices = RemoteDeviceDiscovery
					.getDevices(refresh);

			if (devices != null && devices.size() > 0) {
				for (RemoteDevice d : devices) {
					if (Debug.DEBUG) {
						System.out.println("adding device "
							+ d.getBluetoothAddress());
					}

					BluetoothAddress addr = new BluetoothAddress();
					addr.setName(d.getFriendlyName(false));
					addr.setAddress(d.getBluetoothAddress());
					addr.setAuthenticated(d.isAuthenticated());
					addr.setEncrypted(d.isEncrypted());
					addr.setTrusted(d.isTrustedDevice());

					if (addresses.contains(addr)) {
						addresses.remove(addr);
					}

					addresses.add(addr);

					if (Debug.DEBUG) {
						System.out.println("device added "
							+ d.getBluetoothAddress());
					}
				}

				storeDevices(addresses);
			}

			//
			if (addresses.size() > 0) {

				response.setStatusCode(HttpStatus.SC_OK);
				EntityTemplate body = new EntityTemplate(new ContentProducer() {

					public void writeTo(final OutputStream outstream)
							throws IOException {
						OutputStreamWriter w = new OutputStreamWriter(
								outstream, "UTF-8");
						w.write("<html><body><h3>Bluetooth Device</h3>");
						LocalDevice local = LocalDevice.getLocalDevice();
						w.write("<p>Local: ");
						w.write(local.getFriendlyName());
						w.write(" " + local.getBluetoothAddress());
						w.write("<br />");
						w.write("discoverable: " + local.getDiscoverable());
						w.write("<br />");
						w.write("class: " + local.getDeviceClass().toString());
						w.write("</p>");
						w.write("<ul>");

						for (BluetoothAddress d : addresses) {
							if (d.getAddress() == null) {
								continue;
							}
							String name = d.getName();
							if (name == null) {
								name = d.getAddress();
							}
							w.write("<li>");
							w.write(name);
							w.write("&nbsp;");
							final String addr = d.getAddress();
							w.write("<a target=\"_blank\" href=\"/bin/connect?realm=btspp&address="
									+ addr + "\">" + addr + "</a>");
							w.write("&nbsp;");
							w.write("authenticated: " + d.isAuthenticated());
							w.write("&nbsp;");
							w.write("encrypted: " + d.isEncrypted());
							w.write("&nbsp;");
							w.write("trusted: " + d.isTrusted());
							w.write("</li>");
						}

						w.write("</ul></body></html>");
						w.flush();
					}

				});
				body.setContentType("text/html; charset=UTF-8");
				response.setEntity(body);
				return true;
			}

			response.setStatusCode(HttpStatus.SC_OK);
			EntityTemplate body = new EntityTemplate(new ContentProducer() {

				public void writeTo(final OutputStream outstream)
						throws IOException {
					OutputStreamWriter w = new OutputStreamWriter(outstream,
							"UTF-8");
					w.write("<html><body><h1>");
					w.write("No Bluetooth devices found");
					w.write("</h1></body></html>");
					w.flush();
				}

			});
			body.setContentType("text/html; charset=UTF-8");
			response.setEntity(body);
			return true;
		}
	}

	private static void startup() throws Exception {
		if (Debug.INFO) {
			System.out.println("Initializing Bluetooth...");
		}
		
		BlueCove.init();

		BluewebSettings cfg = loadSettings();
		
		final int port = cfg.serverPort;
		
		if (Debug.INFO) {
			System.out.println("Startup server... port: " + port);
		}
		
		
		final BluetoothProxyHandler handler = new BluetoothProxyHandler();

		server = new ProxyServer(port, handler);
		server.startup();
		
		//
		final String paddr = cfg.proxyAddress;
		if (paddr != null) {
			final String realm = "btspp";
			final int pport = cfg.proxyPort;
			
			if (Debug.INFO) {
				System.out.println("Startup proxy... address: " + paddr + " port:" + pport);
			}
	
			proxy = new ServerSocketThread(realm, paddr, pport);
			proxy.setDaemon(true);
			proxy.start();
		}
		
		if (Debug.INFO) {
			System.out.println("Server started.");
		}
		
	}

	private static void shutdown() {
		if (Debug.INFO) {
			System.out.println("Shutdown server...");
		}
		
		if (server != null) {
			server.shutdown();
		}
		server = null;
		
		if (proxy != null) {
			proxy.interrupt();
		}
		proxy = null;
		
		if (Debug.INFO) {
			System.out.println("Sever stopped.");
		}
		
	}

	private static Image createImage(String path, String description) {
		URL imageURL = Main.class.getResource(path);
		if (imageURL == null) {
			if (Debug.DEBUG) {
				System.err.println("Resource not found: " + path);
			}
			return null;
		} else {
			return (new ImageIcon(imageURL, description)).getImage();
		}
	}

	private static void createAndShowGUI() {
		final String caption = "Blueweb 2.0.0";
		
		if (!SystemTray.isSupported()) {
			if (Debug.INFO) {
				System.out.println("SystemTray is not supported");
			}
			return;
		}
		
		Desktop desktop = null;
	    if (Desktop.isDesktopSupported()) {
	        desktop = Desktop.getDesktop();
	    }

		final PopupMenu popup = new PopupMenu();

		final TrayIcon trayIcon = new TrayIcon(createImage(
				"/res/drawable/icon16.gif", "Blueweb 2.0.0"));
		final SystemTray tray = SystemTray.getSystemTray();

		//
		MenuItem aboutItem = new MenuItem("About");
		MenuItem homeItem = new MenuItem("Home");
		Menu bluewebMenu = new Menu("Blueweb");
		MenuItem statusItem = new MenuItem("Status");
		MenuItem startItem = new MenuItem("Start");
		MenuItem stopItem = new MenuItem("Stop");
		MenuItem settingsItem = new MenuItem("Settings");
		MenuItem logItem = new MenuItem("Console");
		MenuItem exitItem = new MenuItem("Exit");

		//
		popup.add(aboutItem);

		popup.addSeparator();
		if (desktop != null) {
			popup.add(homeItem);
		}
		popup.add(bluewebMenu);
		bluewebMenu.add(statusItem);
		bluewebMenu.add(startItem);
		bluewebMenu.add(stopItem);
		popup.add(settingsItem);
		popup.add(logItem);
		popup.addSeparator();
		popup.add(exitItem);

		trayIcon.setPopupMenu(popup);

		try {
			tray.add(trayIcon);
		} catch (AWTException e) {
			if (Debug.INFO) {
				System.out.println("TrayIcon could not be added.");
			}
			return;
		}

		trayIcon.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane
				.showMessageDialog(null, "Blueweb 2.0.0 for Windows");
			}
		});

		aboutItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JOptionPane
						.showMessageDialog(null, "Blueweb 2.0.0 for Windows");
			}
		});

		homeItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (server == null) {
					trayIcon.displayMessage(caption, "Server not started",
							TrayIcon.MessageType.WARNING);
					return;
				}
				try {
					URI uri = new URI("http://localhost:"+server.getPort()+"/");
					Desktop.getDesktop().browse(uri);
				} catch (Exception ex) {
					JOptionPane.showMessageDialog(null, ex.getMessage());
				}
			}
		});

		startItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					startup();

					trayIcon.displayMessage(caption, "Server started",
							TrayIcon.MessageType.NONE);
				} catch (Exception ex) {
					JOptionPane.showMessageDialog(null, ex.getMessage());
				}
			}
		});

		stopItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					shutdown();
					//
					trayIcon.displayMessage(caption, "Server stopped",
							TrayIcon.MessageType.NONE);
				} catch (Exception ex) {
					JOptionPane.showMessageDialog(null, ex.getMessage());
				}
			}
		});
		
		statusItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String msg = (server != null ? "Server listening on " + server.getPort() :
					"Server stopped.");
				JOptionPane.showMessageDialog(null, msg);
			}
		});
		

		settingsItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					final List<BluetoothAddress> addr = loadDevices();
					final BluewebSettings cfg = loadSettings();
					SettingsDialog dia =  new SettingsDialog(null, true, addr, cfg);
					dia.setVisible(true);
					
					if (Debug.DEBUG) {
						System.out.println("saving " + cfg);
					}
					
					storeSettings(cfg);
					
				} catch (Exception ex) {
					ex.printStackTrace();
				}
			}
		});
		
		final JFrame logframe = new ConsoleFrame(null, 500, 400, false);
		
		logItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				//toggle
				boolean b = logframe.isVisible();
				logframe.setVisible(!b);
			}
		});

		exitItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				shutdown();
				//
				tray.remove(trayIcon);
				System.exit(0);
			}
		});
		
		//
		trayIcon.setToolTip(caption);
		
		trayIcon.displayMessage(caption, "By Li Qiang",
				TrayIcon.MessageType.NONE);
		
	}

	public static void main(String[] args) throws Exception {
		try {
			UIManager
					.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
			// UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
		} catch (UnsupportedLookAndFeelException ex) {
			ex.printStackTrace();
		} catch (IllegalAccessException ex) {
			ex.printStackTrace();
		} catch (InstantiationException ex) {
			ex.printStackTrace();
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
		}

		UIManager.put("swing.boldMetal", Boolean.FALSE);

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				createAndShowGUI();
			}
		});
	}

}
